Conversation
| raise VerifyAccessTokenError("Token missing 'iss' claim") | ||
|
|
||
| # Normalize issuer for validation | ||
| normalized_iss = normalize_domain(unverified_iss) |
There was a problem hiding this comment.
normalize_domain here can raise ValueError if iss claim value is malformed.
When that happens, the control hits to the except of this try block. Which will go un-handled.
I think malformed token claims should result also result in VerifyAccessTokenError. It's better to double check what's happening today without MCD in such cases.
| raise VerifyAccessTokenError("Discovery metadata missing 'issuer' field") | ||
|
|
||
| # Normalize discovery issuer for comparison | ||
| normalized_discovery_issuer = normalize_domain(discovery_issuer) |
There was a problem hiding this comment.
| ) | ||
|
|
||
| # Normalize domains from resolver | ||
| allowed_domains = [normalize_domain(d) for d in result] |
There was a problem hiding this comment.
Same issue as https://github.com/auth0/auth0-api-python/pull/71/changes#r2876890884.
But, here it should throw ConfigurationError.
| domain = issuer.replace('https://', '').replace('http://', '').rstrip('/') | ||
| else: | ||
| domain = self.options.domain | ||
| cache_key = normalize_domain(f"https://{domain}") |
There was a problem hiding this comment.
This line in the else block can be hit in the following scenarios.
When the customer hasn't configured domain (Only has domains), the variable cache_key will have the value https://None/.
This is an API as client use-case, and I think we should throw ConfigurationError in such cases.
This is how I am handling this case on auth0-api-js: https://github.com/auth0/auth0-auth-js/pull/120/changes#diff-2333b3f945f6322231360695305103bd95f929eb3754065f3720c1e4746cc7f2R126-R129
| self._discovery_cache = options.cache_adapter | ||
| self._jwks_cache = options.cache_adapter |
There was a problem hiding this comment.
Since _discovery_cache and _jwks_cache use the same cache adapter, should we add a prefix to keys (like discovery: and jwks:) to keep them separate ?
If a custom adapter changes keys (for example removes parts of the URL or uses a simple hash), a discovery key and a JWKS key can become the same key. Then one value can overwrite the other.
| self._discovery_cache = options.cache_adapter | ||
| self._jwks_cache = options.cache_adapter | ||
| else: | ||
| self._discovery_cache = InMemoryCache(max_entries=options.cache_max_entries) |
There was a problem hiding this comment.
Should we validate cache_max_entries and cache_ttl_seconds at init time (for example cache_max_entries >= 1 and cache_ttl_seconds >= 0) and raise ConfigurationErro on invalid values ?
There was a problem hiding this comment.
In this SDK, we have a provision of devs can bring their Distributed Cache so if they want a real time sync then the condition cache_ttl_seconds >= 0 will cause issues for them.
There was a problem hiding this comment.
Real time sync can be achieved by setting cache_ttl_seconds to 0 right ?
| domain = self.options.domain | ||
| cache_key = normalize_domain(f"https://{domain}") | ||
|
|
||
| cached = self._discovery_cache.get(cache_key) |
There was a problem hiding this comment.
Should we add per-key inflight deduplication for discovery/JWKS cache misses , so concurrent requests for the same issuer/JWKS URI don’t trigger duplicate network fetches ?
| ``` | ||
|
|
||
| > [!NOTE] | ||
| > The resolver runs synchronously. If your lookup requires async I/O (database queries, HTTP calls), wrap it with `asyncio.run()` or pre-load the domain mapping at startup. |
There was a problem hiding this comment.
The resolver runs synchronously.
Can you confirm if this is accurate ?
|
|
||
| ### Host-Header Based | ||
|
|
||
| Route allowed domains based on the incoming request's host: |
There was a problem hiding this comment.
Should we add the same Host/X-Forwarded-Host security note here ?
I am adding it like this in auth0-api-js:
https://github.com/auth0/auth0-auth-js/blob/2f7e16a2339ac9f9a60b5b11d6edd34f3beb5c6e/packages/auth0-api-js/EXAMPLES.md?plain=1#L107-L113
📋 Changes
This PR implements Multiple Custom Domain (MCD) support for auth0-api-python, enabling APIs to accept tokens from multiple Auth0 custom domains with static lists, dynamic resolvers, and hybrid mode for zero-downtime domain migrations.
✨ Features
domainsparameter (static list or callable resolver) onApiClientOptionsdomainanddomainstogether for migration scenarios —domaindrives client-initiated flows (token exchange, connection tokens),domainsdrives token verificationDomainsResolvercallable with request context (DomainsResolverContext)CacheAdapterABC allows custom backends (Redis, Memcached, etc.) with a defaultInMemoryCacheimplementation🔧 API Changes
ApiClientOptionswith MCD parameters:domains,cache_ttl_seconds,cache_max_entries,cache_adapterrequest_urlandrequest_headersparameters toverify_access_token()andverify_request()for resolver contextDomainsResolverContext(TypedDict),DomainsResolver(type alias)ConfigurationError(invalid SDK config, status 500),DomainsResolverError(resolver failure, status 500)CacheAdapter(ABC),InMemoryCache(default LRU cache with TTL)📖 Documentation
README.mdwith MCD feature callout and new section 7 (Multi-Custom Domain Support)docs/MultipleCustomDomain.md— configuration modes, resolver patterns, error handling, migration guidedocs/Caching.md— default behavior, custom adapters (Redis example), tuning recommendations🧪 Testing
Manual Integration Testing
Requires an Auth0 tenant with multiple custom domains configured and a machine-to-machine application with client credentials grant enabled.
Expected: All three domains succeed. Each token's
issmatches its issuing domain.Contributor Checklist